#!/usr/bin/node

require('dotenv').config(); // 加载 .env 中的环境变量

// ToDo: 1. 完善对请求审查，对异常的请求返回 400


const log4js = require('log4js');
const path = require('path');
const config = require('./config');
log4js.configure({
    appenders: {
        "allLogs": {
            type: 'dateFile',
            filename: __dirname + '/log/all',
            pattern: 'yyyy-MM-dd.log',
            alwaysIncludePattern: true,
            encoding: 'utf-8',
            backups: 2,
        },
        "infoLogs": {
            type: 'file',
            filename: __dirname +  '/log/info.log',
            encoding: 'utf-8',
        },
        "email": {
            type: "@log4js-node/smtp",
            transport: {
                plugin: 'smtp',
                options: {
                    host: config.smtp_host,
                    port: config.smtp_port,
                    auth: {
                        user: config.smtp_user,
                        pass: config.smtp_pass
                    }
                }
            },
            sender: config.smtp_user,
            subject: 'Error Report - Logger',
            recipients: config.err_to,
            html: true,
        },
    },
    categories: {
        "default": {
            appenders: ["allLogs"],
            level: "all",
        },
        "info": {
            appenders: ["infoLogs"],
            level: "info",
        },
        'err':{
            appenders: ['email'],
            level: 'error'
        }
    }
});

var allLogger = log4js.getLogger('default');
var infoLogger = log4js.getLogger('info');
var errLogger = log4js.getLogger('err');

/**
 * @function 写入Log数据
 * @param {String} level 级别 TRACE<DEBUG<INFO<WARN<ERROR<FATAL<MARK
 * @param {String} msg 日志内容
 * @returns {Boolean}  FLAG
 */
function writeLog(level, msg){
    if (level === 'TRACE'){
        allLogger.trace(msg);
    } else if (level === 'DEBUG'){
        allLogger.debug(msg);
    } else if (level === 'INFO'){
        allLogger.info(msg);
        infoLogger.info(msg);
    } else if (level === 'WARN'){
        allLogger.warn(msg);
    } else if (level === 'ERROR'){
        allLogger.error(msg);
        errLogger.error(msg);
    } else if (level === 'FATAL'){
        allLogger.fatal(msg);
        errLogger.fatal(msg);
    } else if (level === 'MARK'){
        allLogger.mark(msg);
        errLogger.mark(msg);
    } else {
        return false;
    }
    console.log('[' + level + '] ' + msg);
    return true;
}





writeLog('TRACE', '启动服务器');


const template = require('art-template');

/**
 * @function 渲染模板
 * @param {String} template 模板文件路径
 * @param {Query} query 数据字典
 * @returns {String} HTML字符串
 */
function renderTemplate(templatePath, query){
    const filePath = path.join(__dirname, templatePath);
    var html = template(filePath, query);
    return html;
}

/**
 * @function 返回一个页面,显示Message,并在3秒后跳转至URL
 * @param {http.ServerResponse} res 
 * @param {int} code 状态码
 * @param {String} message 要显示的消息
 * @param {String} url 要跳转的URL
 * @returns {void}
 */
function sendMessageWithURL(res, code, message, url){
    res.writeHead(code, {
        "Content-Type": "text/html; charset=utf-8"
    });
    res.end( renderTemplate('template/message.art', {
        message: message,
        url: url,
    }) );
}



/**
 * 邮件通知 Todo: 完善
 */
const nodemailer = require('nodemailer');

// 创建SMTP传输器对象
const transporter = nodemailer.createTransport({
  host: config.smtp_host,
  port: config.smtp_port,
  secure: true, // 如果使用的是TLS/STARTTLS，则将其设置为false；如果使用的是SSL，则将其设置为true
  auth: {
    user: config.smtp_user,
    pass: config.smtp_pass,
  }
});

function sendReminderEmail(user_name, supervisor_name, supervisor_email, action, task_data, time, log_note){
    var event_summary = `${action}了  ${task_data}`; //'<开始/结束>了 <分类>-<任务简写>';
    var event = `<b>${time}</b>，${user_name} ${action}了 <b>${task_data}</b> 任务, 备注信息：“${log_note}”`; //`<准确时间>, Ta <开始/结束>了 <b><分类>-<任务简写></b> 任务, 备注信息 "${log_note}"`;
    var subject = `${user_name}  ${event_summary}  任务`;

var email_text = ` \
${user_name}  ${event}
${supervisor_name}:
你好!
    这是来自 "时光守望"监督系统 的邮件. ${user_name} 将您作为了监督者, 请帮助 Ta 监督学习与工作, 在此向您表达诚挚的感谢. </p>
    此为系统邮件, 不要回复.
`;

var email_html = ` \
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f6f6f6;
      margin: 0;
      padding: 0;
    }

    .container {
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
      background-color: #ffffff;
      border-radius: 4px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    }

    h1 {
      color: #333333;
      margin: 0;
      padding: 0;
    }

    p {
      color: #666666;
      font-size: 18pt;
    }

    .button {
      display: inline-block;
      margin-top: 20px;
      padding: 10px 20px;
      background-color: #71e3a9;
      color: #ffffff;
      text-decoration: none;
      border-radius: 4px;
      font-size: 18px;
    }

    .footer {
      margin-top: 20px;
      text-align: center;
      color: #999999;
      font-size: 12px;
    }
  </style>
</head>
<body>
  <div class="container">
    <p>${supervisor_name}:</p>
    <p>&nbsp;&nbsp;&nbsp;&nbsp;你好!</p>
    <p>&nbsp;&nbsp;&nbsp;&nbsp;${event}</p>
    <a href="http://yschn.f3322.net:85/index" class="button">访问监督站点</a>
    <p>&nbsp;&nbsp;&nbsp;&nbsp;——这是来自 “时光守望”监督系统 的邮件。${user_name} 将您作为了监督者，请帮助 Ta 监督学习与工作，在此向您表达诚挚的感谢。</p>
    <div class="footer">
      此电子邮件由通知系统自动发送
    </div>
  </div>
</body>
</html>
`;
    transporter.sendMail({
        from: config.smtp_user,
        to: supervisor_email,
        subject: subject,
        text: email_text,
        html: email_html,
    }, (error, info) => {
        if (error) {
            writeLog('ERROR', 'Error occurred: ' + error.message);
            return false;
        } else {
            return true;
        }
      });
}


const querystring = require('querystring');

/**
 * 获取 POST | PUT | PATCH | DELETE 的数据
 * @param {http.IncomingMessage} request 
 * @param {Function} callback res, query: 数据(字典格式)
 */
function getPostData(request, response, callback){
    let body = '';
    
    request.on('data', chunk => {
        body += chunk.toString(); // 将接收的数据片段转为字符串
    });

    request.on('end', () => {
        let query;

        try {
            query = JSON.parse(body);
        } catch(e) {
            // 如果 JSON 解析失败，尝试使用查询字符串解析
            query = querystring.parse(body);
        }
        
        callback(response, query);
    });
}




const express = require('express');
const app = express();
app.use(express.json()); // 解析 JSON 数据
app.use(express.urlencoded({ extended: true })); // 解析 URL 编码数据
const cookieParser = require('cookie-parser')
app.use(cookieParser()) // 解析 Cookie
const compression = require('compression');
app.use(compression());


const TimeGuard = require('./TimeGuard');



const cacheConfig = {
    /* 缓存一小时 */
    '/static/vue-style.css': 'public, max-age=3600',
    '/static/js/index.js': 'public, max-age=3600',
    '/static/js/message.js': 'public, max-age=3600',
    '/static/js/submitForm.js': 'public, max-age=3600',

    /* 缓存一年 */
    '/static/switch-01.png': 'public, max-age=31536000',
    '/static/favicon.ico': 'public, max-age=31536000',
    '/static/jquery-3.6.3.slim.min.js': 'public, max-age=31536000',
    '/static/FZYouHei_504L.woff2': 'public, max-age=31536000',
    '/static/switch-01.png': 'public, max-age=31536000'
};
const setCache = function (req, res, next) {
    var CacheControl = 'no-store';
    if(cacheConfig[req.url]){
        CacheControl = cacheConfig[req.url];
    }

    res.setHeader('Cache-Control', CacheControl); // 设置资源的最长缓存时间

    next();
}

app.use(setCache);
app.use('/static', express.static('static'));

app.get('/robots.txt', function(_, res) {
    res.setHeader('Cache-Control', 'public, max-age=604800'); // 缓存一周
    res.sendFile(path.join(__dirname, 'static', '/robots.txt'));
});




var getIndexPage = (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){ // 成功
        // 跳转到未存档的 Log
        var log = defLog.get_log_by_end_NULL();
        if (req.url === '/' && log) {
            res.redirect(`/submit?mode=log&logID=${log['ID']}`);
        }else{
            res.writeHead(200, {
                "Content-Type": "text/html; charset=utf-8"
            });
            res.end(renderTemplate('template/index.art', defLog.getIndexData()));
        }
    }else{ // 失败
        res.redirect('/login');
    }

    defLog.close();
};

// 处理 index 页面请求
app.get('/', getIndexPage);
app.get('/index', getIndexPage);

// 处理 all 页面请求
app.get('/all', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8"
        });
        res.end(renderTemplate('template/allLog.art', defLog.getAllLogData( req.query )));
    }else{ // 失败
        res.redirect('/login');
    }
    defLog.close();
});

// 处理 submit 页面请求
app.get('/submit', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        var query = req.query;

        if (query['mode'] === 'add') {
            if (!query['taskID']){
                writeLog('TRACE', 'Req: ' + req.url + ' > [400] Error: Bad Request.');
                sendMessageWithURL(res, 400, '<h1>400</h1><br><b>Error: Bad Request.</b>');
                return;
            }

            // 检测是否有未存档的 Log, 如有就阻止创建
            if (defLog.getUnarchivedLog()) {
                writeLog('TRACE', 'Req: ' + req.url + ' > Refuse.');
                sendMessageWithURL(res, 405, '<h1>405</h1><br><b>存在未存档的日志, 禁止创建</b>', '/index');
                return;
            }

            // 获取已知任务数据
            var task = defLog.getTaskDataByID(query);
            if (!task) {
                writeLog('TRACE', 'Req: ' + req.url + ' > Refuse.');
                sendMessageWithURL(res, 404, '<h1>404</h1><br><b>不存在该任务</b>', '/index');
                return;
            }
            if (task['doneFlag'] == true) {
                writeLog('TRACE', 'Req: ' + req.url + ' > Refuse.');
                sendMessageWithURL(res, 405, '<h1>405</h1><br><b>任务已关闭，禁止创建日志</b>', '/index');
                return;
            }
            if (!task['quantity'] || task['quantity'] == 'null') {
                query['taskData'] = task['category'] + '-' + task['summary'];
            } else {
                query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            }
            //query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            query['progress'] = task['progress'];
            query['taskNote'] = task['note'];

            query['startTime'] = '';
            query['endTime'] = '';
            query['quantity'] = '';
            query['logNote'] = '';

            res.writeHead(200, {
                "Content-Type": "text/html; charset=utf-8"
            });
            res.end(renderTemplate('template/submit.art', query));
        }else if (query['mode'] === 'log'){
            if (!query['logID']){
                writeLog('TRACE', 'Req: ' + req.url + ' > [400] Error: Bad Request.');
                sendMessageWithURL(res, 400, '<h1>400</h1><br><b>Error: Bad Request.</b>', '/index');
                return;
            }

            // 获取 log 数据, 已知任务数据
            var log = defLog.getLogDataByID(query);
            if (!log){
                writeLog('TRACE', 'Req: ' + req.url + ' > Refuse.');
                sendMessageWithURL(res, 404, '<h1>404</h1><br><b>不存在该日志</b>', '/index');
                return;
            }
            // 判断是否已存档
            if (log['end'] != null){
                writeLog('TRACE', 'Req: ' + req.url + ' > Refuse.');
                sendMessageWithURL(res, 405, '<h1>405</h1><br><b>日志已存档，禁止修改</b>', '/index');
                return;
            }
            //console.log(log);
            var task = defLog.getTaskDataByID(log);
            query['taskID'] = log['taskID'];
            query['startTime'] = log['start'];
            //console.log(task['quantity'])
            if (!task['quantity'] || task['quantity'] == 'null') {
                query['taskData'] = task['category'] + '-' + task['summary'];
            } else {
                query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            }
            //query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            query['endTime'] = '';
            query['quantity'] = log['quantity'];
            query['logNote'] = log['note'];

            query['progress'] = task['progress'];
            query['taskNote'] = task['note'];

            res.writeHead(200, {
                "Content-Type": "text/html; charset=utf-8"
            });
            //console.log(query);
            res.end(renderTemplate('template/submit.art', query));
        }
    }else{ // 失败
        res.redirect('/login');
    }

    defLog.close();
});

// 处理 update 接口请求 & 新建
app.post('/api/v1/logs', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        var query = req.body;
        var logID = defLog.insertLog(query);
                
        res.setHeader('Content-Type', 'application/json');
        res.end(JSON.stringify({
            "code": 200,
            "action": `window.location.replace('/submit?mode=log&logID=${logID}');`
        }));


        // 发送提示邮件
        var logData = defLog.getLogDataByID({logID: logID});
        var time = logData.date + '&emsp;' + logData.start;
        var task = defLog.getTaskDataByID({taskID: logData.taskID});
        if (!task['quantity'] || task['quantity'] == 'null') {
            query['taskData'] = task['category'] + '-' + task['summary'];
        } else {
            query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
        }
        var supervisors = defLog.getSupervisors();
        var userData = defLog.getUserData();
        for(let item of supervisors) {
            sendReminderEmail(userData.user_name, item.user_name, item.email, '开始', query.taskData, time, query.logNote);
        }
    }else{ // 失败
        res.redirect('/login');
    }

    defLog.close();
});

// 处理 update 接口请求 & 更新
app.patch('/api/v1/logs', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        var query = req.body;
        defLog.updateTask(query); 

        if (query['logStatus'] == 'on') { // 存档 log 数据
            defLog.archiveLog(query);

            res.setHeader('Content-Type', 'application/json');
            res.end(JSON.stringify({
                "code": 200,
                "action": "window.location.replace('/');"
            }));


            // 发送提示邮件
            var logData = defLog.getLogDataByID(query);
            var time = logData.date + '&nbsp;&nbsp;' + logData.start;
            var task = defLog.getTaskDataByID({taskID: logData.taskID});
            if (!task['quantity'] || task['quantity'] == 'null') {
                query['taskData'] = task['category'] + '-' + task['summary'];
            } else {
                query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            }
            var supervisors = defLog.getSupervisors();
            var userData = defLog.getUserData();
            for(let item of supervisors) {
                sendReminderEmail(userData.user_name, item.user_name, item.email, '结束', query.taskData, time, query.logNote);
            }
        } else { // 更新 log 数据
            defLog.updateLog(query);

            res.setHeader('Content-Type', 'application/json');
            res.end(JSON.stringify({
                "code": 200,
                "action": ''
            }));
        }
    }else{ // 失败
        res.redirect('/login');
    }
    
    defLog.close();
});

// 处理 setTaskTargetFlag 接口请求 - 优先级标记
app.put('/api/v1/task_tag', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        var query = req.body;
        var targetFlag = defLog.getTaskDataByID(query).targetFlag;
        switch(query.mode){
            case 'inverse':
                if(targetFlag == 0){
                    query['targetFlag'] = 50;
                } else {
                    query['targetFlag'] = (targetFlag + 100) % 200;
                }
                defLog.setTaskTargetFlagByID(query);
                break;
            case 'high': // (近期)待完成 | 高优先
                query['targetFlag'] = 150;
                defLog.setTaskTargetFlagByID(query);
                break;
            case 'low': // (近期)已完成 | 低优先
                query['targetFlag'] = 50;
                defLog.setTaskTargetFlagByID(query);
                break;
            case 'none': // 最低优先级 (例: 睡觉)
                query['targetFlag'] = 0;
                defLog.setTaskTargetFlagByID(query);
                break;
            case 'inverse-none': // 在“低优先”与“无优先”之间切换
                query['targetFlag'] = (targetFlag + 50) % 100;
                defLog.setTaskTargetFlagByID(query);
                break;
        }

        res.setHeader('Content-Type', 'application/json');
        res.end(JSON.stringify({
            "code": 200,
            'taskID': query.taskID,
            'targetFlag': query.targetFlag
        }));
    }else{ // 失败
        res.redirect('/login');
    }

    defLog.close();
});

// 处理 create 页面请求 - 创建任务
app.get('/create', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8"
        });
        writeLog('TRACE', 'Req: ' + req.url + ' > template/create.art')
        res.end(renderTemplate('template/create.art', {
            category: '',
            summary: '',
            quantity: '',
            totalMemo: '',
        }));
    }else{ // 失败
        res.redirect('/login');
    }
});

// 处理 tasks 接口请求 & 创建任务
app.post('/api/v1/tasks', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        var query = req.body;
        defLog.createTask(query);

        res.setHeader('Content-Type', 'application/json');
        res.end(JSON.stringify({
            "code": 200,
            "action": "window.location.replace('/index');"
        }));
    }else{ // 失败
        res.redirect('/login');
    }

    defLog.close();
});


// 处理 login 页面请求
app.get('/login', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.redirect('/');
    }

    res.writeHead(200, {
        "Content-Type": "text/html; charset=utf-8"
    });
    writeLog('TRACE', 'Req: ' + req.url + ' > template/login.art')
    res.end(renderTemplate('template/login.art', {}));
});

// 处理 login 接口请求
app.post('/api/v1/login', (req, res) => {
    var defLog = new TimeGuard();
    var data = defLog.login(req.body.account_name, req.body.password);
    res.setHeader('Content-Type', 'application/json');

    if (data.success == 1){
        const cookieConfig = {
            httpOnly: true, // 设置该Cookie只可以由服务端访问，即前端JavaScript无法访问document.cookie获取该值，但控制台还是可以查看和修改
            // secure: true, 只有通过HTTPS请求的Cookie才被使用，否则都认为是错误的Cookie
            // domain: 'localhost', // 设置保存Cookie的域名，浏览器查找Cookie时，子域名（如translate.google.com）可以访问主域名（google.com）下的Cookie，而主域名（google.com）不可以访问子域名（如translate.google.com）下的Cookie
            path: '/', // 设置保存Cookie的路径，浏览器查找Cookie时，子路径（如/map）可以访问根路径（'/'）下设置的Cookie，而根路径（'/'）无法访问子路径（如/map）下设置的Cookie
            
            // expires: new Date(new Date().getTime() + 14 * 86400000), // 通过expires设置Cookie过期时间为14天后
            maxAge: 14 * 86400000, // 通过maxAge设置Cookie过期时间为14天后
        };
        res.cookie('token', data.token, cookieConfig);
        var action = "window.location.href = '/';";
        if (req.body.password === '123456'){
            action = "window.location.href = '/resetPassword';";
        }
        res.end(JSON.stringify({
            "code": 200,
            "success": 1,
            "action": action,
            "msg": '登录成功'
        }));
    }else{
        res.end(JSON.stringify({
            "code": 200,
            "success": 0,
            "msg": '登录失败'
        }));
    }
    
    defLog.close();
});

// 处理 logout 接口请求
app.get('/api/v1/logout', (_, res) => {
    res.clearCookie('account_name');
    res.clearCookie('token');
    res.redirect('/login');
});

// 处理 resetPassword 页面请求
app.get('/resetPassword', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8"
        });
        writeLog('TRACE', 'Req: ' + req.url + ' > template/resetPassword.art')
        res.end(renderTemplate('template/resetPassword.art', {}));
    }else{
        res.redirect('/login');
    }

    defLog.close();
});

// 处理 password 接口请求 & 更改密码
app.put('/api/v1/password', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.setHeader('Content-Type', 'application/json');
        if(defLog.resetPassword(req.body.original_password, req.body.new_password)){
            res.clearCookie('account_name');
            res.clearCookie('token');
            res.end(JSON.stringify({
                "code": 200,
                "success": 1
            }));
        }else{
            res.end(JSON.stringify({
                "code": 200,
                "success": 0,
                "msg": '更改失败, 请检查原密码'
            }));
        }
    }else{
        res.redirect('/login');
    }

    defLog.close();
});

// 处理 currentLogs 接口请求 & 获取当前记录
app.get('/api/v1/current_log', (req, res) => {
    var defLog = new TimeGuard();
    if(defLog.verify(req.cookies.token)){
        res.writeHead(200, {
            "Content-Type": "text/html; charset=utf-8"
        });
        current_log = defLog.getCurrentLog();
        if(!current_log){
            res.end(JSON.stringify({
                "code": 200,
                "msg": '当前无记录'
            }));
        }else{
            var task = defLog.getTaskDataByID(current_log);
            var query = {
                'code': 200,
                'msg': '获取成功'
            };
            query['taskID'] = current_log['taskID'];
            query['startTime'] = current_log['start'];
            //console.log(task['quantity'])
            if (!task['quantity'] || task['quantity'] == 'null') {
                query['taskData'] = task['category'] + '-' + task['summary'];
            } else {
                query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            }
            //query['taskData'] = task['category'] + '-' + task['summary'] + '  [' + task['quantity'] + ']';
            query['endTime'] = '';
            query['quantity'] = current_log['quantity'];
            query['logNote'] = current_log['note'];

            query['progress'] = task['progress'];
            query['taskNote'] = task['note'];

            res.end(JSON.stringify(query));
        }
    }else{
        res.redirect('/login');
    }

    defLog.close();
});


app.listen(config.listenPort, () => {
    console.log(`Server is listening on port ${config.listenPort}`);
});
